/*******************************************************************************
 * *
 * *             COPYRIGHT 2012 CLEARLEAP, INC. ALL RIGHTS RESERVED.
 * *             CLEARLEAP CONFIDENTIAL PROPRIETARY
 * *             TEMPLATE VERSION R01.00
 * *
 * ********************************************************************************
 * *
 * *  FILE NAME: XmlDigitalSignatureVerifier.java
 * *
 * *---------------------------------- PURPOSE ------------------------------------
 * *
 * * Utilities to verify XML document signature
 * *
 * *
 * *----------------------------- REVISION HISTORY --------------------------------
 * *
 * * Date      ID        Tracking#      Description
 * * --------  --------  -------------  -------------------------------------------
 * * 01.07                              Created
 * *
 * *--------------------------- INCLUDES ----------------------------*
 * *
 ******************************************************************************/
package com.easysale.util;

import java.security.Key;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.Iterator;

import javax.xml.crypto.AlgorithmMethod;
import javax.xml.crypto.KeySelector;
import javax.xml.crypto.KeySelectorException;
import javax.xml.crypto.KeySelectorResult;
import javax.xml.crypto.XMLCryptoContext;
import javax.xml.crypto.XMLStructure;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.X509Data;

import org.w3c.dom.Node;

public final class XmlDigitalSignatureVerifier {
    private XMLSignatureFactory xmlSignatureFactory;

    public XmlDigitalSignatureVerifier() {
        xmlSignatureFactory = XMLSignatureFactory.getInstance("DOM");
    }

    public void verify(Node signatureNode) throws Exception {
        try {
            // The embedded key must match our signatureKey's values or the
            // validation will fail.
            DOMValidateContext valContext = new DOMValidateContext(
                    new X509KeySelector(), signatureNode);

            XMLSignature signature = xmlSignatureFactory
                    .unmarshalXMLSignature(valContext);
            if (!signature.validate(valContext)) {
                boolean isSignatureValueValid = signature.getSignatureValue()
                        .validate(valContext);
                StringBuilder exceptionText = new StringBuilder();
                exceptionText
                        .append("Invalid signature. SignatureValue ")
                        .append(isSignatureValueValid ? "validated" : "INVALID");
                Iterator<?> i = signature.getSignedInfo().getReferences()
                        .iterator();
                for (int j = 0; i.hasNext(); j++) {
                    boolean refValid = ((Reference) i.next())
                            .validate(valContext);
                    exceptionText.append("; ")
                            .append(getReferenceNameForOpenESignFormsOnly(j))
                            .append(refValid ? " validated" : " INVALID");
                }
                throw new Exception(exceptionText.toString());
            }
        } catch (Exception e) {
            throw e;
        }
    }

    private static String getReferenceNameForOpenESignFormsOnly(int i) {
        String result;
        if (i == 0) {
            result = "XML data payload";
        } else if (i == 1) {
            result = "OpenESignForms Seal";
        } else {
            result = "Unexpected Reference[" + i + "]";
        }
        return result;
    }

    private class X509KeySelector extends KeySelector {
        public KeySelectorResult select(KeyInfo keyInfo,
                KeySelector.Purpose purpose, AlgorithmMethod method,
                XMLCryptoContext context) throws KeySelectorException {
            Iterator ki = keyInfo.getContent().iterator();
            while (ki.hasNext()) {
                XMLStructure info = (XMLStructure) ki.next();
                if (!(info instanceof X509Data)) {
                    continue;
                }
                X509Data x509Data = (X509Data) info;
                Iterator xi = x509Data.getContent().iterator();
                while (xi.hasNext()) {
                    Object o = xi.next();
                    if (!(o instanceof X509Certificate)) {
                        continue;
                    }
                    final PublicKey key = ((X509Certificate) o).getPublicKey();
                    // Make sure the algorithm is compatible
                    // with the method.
                    if (algEquals(method.getAlgorithm(), key.getAlgorithm())) {
                        return new KeySelectorResult() {
                            public Key getKey() {
                                return key;
                            }
                        };
                    }
                }
            }
            throw new KeySelectorException("No key found!");
        }

        private boolean algEquals(String algURI, String algName) {
            return (algName.equalsIgnoreCase("DSA") && algURI
                    .equalsIgnoreCase(SignatureMethod.DSA_SHA1))
                    || (algName.equalsIgnoreCase("RSA") && algURI
                            .equalsIgnoreCase(SignatureMethod.RSA_SHA1));
        }
    }
}
